package com.xiaoxx.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaoxx.coin.enums.*;
import com.xiaoxx.coin.impl.UMFuturesClientImpl;
import com.xiaoxx.coin.impl.futures.Market;
import com.xiaoxx.coin.po.AccountPositon;
import com.xiaoxx.coin.po.PriceChange;
import com.xiaoxx.coin.po.res.*;
import com.xiaoxx.common.error.ErrorException;
import com.xiaoxx.config.ThreadUtils;
import com.xiaoxx.mapper.BinanceTickerPriceMapper;
import com.xiaoxx.model.dto.coin.CommonOrderDto;
import com.xiaoxx.model.entity.coin.*;
import com.xiaoxx.model.vo.coin.BinancePriceChangeCacheVo;
import com.xiaoxx.model.vo.coin.PriceChangeVo;
import com.xiaoxx.service.*;
import com.xiaoxx.utils.date.LocalDateTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author HX
 * @since 2024-11-28
 */
@Service
@Slf4j
public class BinanceTickerPriceServiceImpl extends ServiceImpl<BinanceTickerPriceMapper, BinanceTickerPrice> implements BinanceTickerPriceService {
    @Autowired
    private BinanceExchangeinfoUserService exchangeinfoUserService;
    @Autowired
    private BinanceExchangeinfoService exchangeinfoService;
    @Autowired
    private BinanceApikeyService apikeyService;
    @Autowired
    private BinanceTradingService tradingService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private BinanceOderService binanceOderService;


    @Scheduled(cron = "0/10 * * * * *")
    public void getSymbolPrice() {
        Map<String, BinanceExchangeinfo> exchangeinfoMap = exchangeinfoService.list().stream().collect(Collectors.toMap(BinanceExchangeinfo::getSymbol, i -> i));
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        Market market = new UMFuturesClientImpl().market();
        List<TickerPriceResponse> tickerPrices = market.tickerPrice(parameters);
        List<BinanceTickerPrice> binanceTickerPrices = tickerPrices.stream()
                .filter(i -> i.getSymbol().endsWith("USDT"))
                .filter(i -> i.getTime() >= LocalDateTimeUtil.parseMillisecond(LocalDateTimeUtil.getDayStart(LocalDateTime.now())))
                .filter(i -> exchangeinfoMap.keySet().contains(i.getSymbol()))
                .map(i -> {
                    BinanceExchangeinfo binanceExchangeinfo = exchangeinfoMap.get(i.getSymbol());
                    BinanceTickerPrice tickerPrice = new BinanceTickerPrice();
                    tickerPrice.setSymbol(i.getSymbol());
                    tickerPrice.setPrice(i.getPrice());
                    tickerPrice.setPricePrecision(binanceExchangeinfo.getPricePrecision());
                    tickerPrice.setQuantityPrecision(binanceExchangeinfo.getQuantityPrecision());
                    tickerPrice.setPullTime(LocalDateTimeUtil.parseTimestamp(i.getTime()));
                    return tickerPrice;
                }).collect(Collectors.toList());
        this.saveBatch(binanceTickerPrices);
        ThreadUtils.asyncSumbit(() -> {
            cacheSymbolPrice();
        });
    }

    /**
     * 定时取消未成交订单
     */
    @Scheduled(cron = "0/30 * * * * *")
    public void timeOutCancelOrder() {
        List<BinanceApikey> list = apikeyService.getAllList();
        Map<Long, BinanceOrder> binanceOrderMap = binanceOderService.lambdaQuery()
                .eq(BinanceOrder::getStatus, OrderStatusEnum.NEW)
                .eq(BinanceOrder::getType, OrderTypesEnum.TAKE_PROFIT_MARKET)
                .list().stream().collect(Collectors.toMap(BinanceOrder::getBinanceOrderId, i -> i, (v1, v2) -> v1));
        list.forEach(i -> {
            //获取所有挂单
            List<CurrentAllOpenOrdersResponse> currentAllOpenOrdersResponses = tradingService.currentAllOpenOrders(i.getApiKey(), i.getSecretKey());
            // 未下单的的其他止盈止损取消
            AccountBillingInfoResponse accountTrading = tradingService.getAccountTrading(i.getApiKey(), i.getSecretKey());
            List<AccountPositon> positions = accountTrading.getPositions();
            //挂单取消
            if (!currentAllOpenOrdersResponses.isEmpty()) {
                Map<String, List<CurrentAllOpenOrdersResponse>> openOrdersMap = currentAllOpenOrdersResponses.stream().collect(Collectors.groupingBy(CurrentAllOpenOrdersResponse::getSymbol));
                Map<String, AccountPositon> positionsMap = positions.stream().collect(Collectors.toMap(AccountPositon::getSymbol, k -> k, (v1, v2) -> v1));
                openOrdersMap.forEach((key, value) -> {
                    String SYMBOL_KEY = "Order:" + key + ":" + i.getApiKey();
                    Optional<CurrentAllOpenOrdersResponse> limitOrderFirst = value.stream().filter(j -> j.getType().equals(OrderTypesEnum.LIMIT.getType())).findFirst();
                    if (limitOrderFirst.isPresent()) {
                        CurrentAllOpenOrdersResponse limitOrder = limitOrderFirst.get();
                        //超过30秒自动取消
                        if (LocalDateTimeUtil.parseTimestamp(limitOrder.getTime()).isBefore(LocalDateTime.now().minus(40, ChronoUnit.SECONDS))) {
                            tradingService.cancelBatchOrders(i.getApiKey(), i.getSecretKey(), key, value.stream().map(CurrentAllOpenOrdersResponse::getOrderId).collect(Collectors.toList()));
                            if (!positionsMap.containsKey(key) && !isCompareTo(new BigDecimal(positionsMap.get(key).getPositionAmt()), new BigDecimal("0"), 3)) {
                                redisTemplate.delete(SYMBOL_KEY);
                            }
                        }
                    } else {
                        if (!positionsMap.containsKey(key)) {
                            tradingService.cancelBatchOrders(i.getApiKey(), i.getSecretKey(), key, value.stream().map(CurrentAllOpenOrdersResponse::getOrderId).collect(Collectors.toList()));
                            redisTemplate.delete(SYMBOL_KEY);
                        }
                    }
                    //超过60分钟未止盈手动止盈
                    ThreadUtils.asyncSumbit(() -> {
                        String SYMBOL_PRICE_KEY = "binance:" + key;
                        if (!redisTemplate.hasKey(SYMBOL_PRICE_KEY)) {
                            return;
                        }
                        BinancePriceChangeCacheVo cacheVo = JSONObject.parseObject(redisTemplate.opsForValue().get(SYMBOL_PRICE_KEY), BinancePriceChangeCacheVo.class);
                        Optional<CurrentAllOpenOrdersResponse> takeProfitMarketOrderFirst = value.stream().filter(j -> j.getType().equals(OrderTypesEnum.TAKE_PROFIT_MARKET.getType())).findFirst();
                        if (takeProfitMarketOrderFirst.isPresent()) {
                            CurrentAllOpenOrdersResponse takeProfitMarketOrder = takeProfitMarketOrderFirst.get();
                            if (binanceOrderMap.containsKey(takeProfitMarketOrder.getOrderId()) && positionsMap.containsKey(key)) {
                                BinanceOrder binanceOrder = binanceOrderMap.get(takeProfitMarketOrder.getOrderId());
                                AccountPositon accountPositon = positionsMap.get(key);
                                BigDecimal unrealizedProfit = new BigDecimal(accountPositon.getUnrealizedProfit());
                                BigDecimal profit1 = i.getMaxOrderAmount().multiply(new BigDecimal("1")).divide(new BigDecimal("100")).setScale(2, RoundingMode.HALF_DOWN);
                                BigDecimal profit5 = i.getMaxOrderAmount().multiply(new BigDecimal("0.7")).divide(new BigDecimal("100")).setScale(2, RoundingMode.HALF_DOWN);
                                BigDecimal profit15 = i.getMaxOrderAmount().multiply(new BigDecimal("0.4")).divide(new BigDecimal("100")).setScale(2, RoundingMode.HALF_DOWN);
                                BigDecimal profit30 = i.getMaxOrderAmount().multiply(new BigDecimal("0.3")).divide(new BigDecimal("100")).setScale(2, RoundingMode.HALF_DOWN);
                                boolean isBefore1 = LocalDateTimeUtil.parseTimestamp(takeProfitMarketOrder.getTime()).isBefore(LocalDateTime.now().minus(1, ChronoUnit.MINUTES));
                                boolean isBefore5 = LocalDateTimeUtil.parseTimestamp(takeProfitMarketOrder.getTime()).isBefore(LocalDateTime.now().minus(5, ChronoUnit.MINUTES));
                                boolean isBefore15 = LocalDateTimeUtil.parseTimestamp(takeProfitMarketOrder.getTime()).isBefore(LocalDateTime.now().minus(15, ChronoUnit.MINUTES));
                                boolean isBefore30 = LocalDateTimeUtil.parseTimestamp(takeProfitMarketOrder.getTime()).isBefore(LocalDateTime.now().minus(30, ChronoUnit.MINUTES));
                                if (isBefore30 && isCompareTo(unrealizedProfit, profit30, 2)) {
                                    tradingService.createOrder(i.getApiKey(), i.getSecretKey(), CommonOrderDto.builder().symbol(binanceOrder.getSymbol()).side(SideEnum.ofSide(binanceOrder.getSide()))
                                            .positionSide(PositionSideEnum.fromCode(binanceOrder.getPositionSide())).type(OrderTypesEnum.TAKE_PROFIT_MARKET).stopPrice(new BigDecimal(cacheVo.getNewTickerPrice())).userId(i.getUserId())
                                            .quantity(new BigDecimal(accountPositon.getPositionAmt())).build());
                                } else if (isBefore15 && isCompareTo(unrealizedProfit, profit15, 2)) {
                                    tradingService.createOrder(i.getApiKey(), i.getSecretKey(), CommonOrderDto.builder().symbol(binanceOrder.getSymbol()).side(SideEnum.ofSide(binanceOrder.getSide()))
                                            .positionSide(PositionSideEnum.fromCode(binanceOrder.getPositionSide())).type(OrderTypesEnum.TAKE_PROFIT_MARKET).stopPrice(new BigDecimal(cacheVo.getNewTickerPrice())).userId(i.getUserId())
                                            .quantity(new BigDecimal(accountPositon.getPositionAmt())).build());
                                } else if (isBefore5 && isCompareTo(unrealizedProfit, profit5, 2)) {
                                    tradingService.createOrder(i.getApiKey(), i.getSecretKey(), CommonOrderDto.builder().symbol(binanceOrder.getSymbol()).side(SideEnum.ofSide(binanceOrder.getSide()))
                                            .positionSide(PositionSideEnum.fromCode(binanceOrder.getPositionSide())).type(OrderTypesEnum.TAKE_PROFIT_MARKET).stopPrice(new BigDecimal(cacheVo.getNewTickerPrice())).userId(i.getUserId())
                                            .quantity(new BigDecimal(accountPositon.getPositionAmt())).build());
                                } else if (isBefore1 && isCompareTo(unrealizedProfit, profit1, 2)) {
                                    tradingService.createOrder(i.getApiKey(), i.getSecretKey(), CommonOrderDto.builder().symbol(binanceOrder.getSymbol()).side(SideEnum.ofSide(binanceOrder.getSide()))
                                            .positionSide(PositionSideEnum.fromCode(binanceOrder.getPositionSide())).type(OrderTypesEnum.TAKE_PROFIT_MARKET).stopPrice(new BigDecimal(cacheVo.getNewTickerPrice())).userId(i.getUserId())
                                            .quantity(new BigDecimal(accountPositon.getPositionAmt())).build());
                                }
                            }

                        }
                    });
                });
            }
            ThreadUtils.asyncSumbit(() -> {
                i.setTotalUnrealizedProfit(accountTrading.getTotalUnrealizedProfit());
                i.setTotalWalletBalance(accountTrading.getTotalWalletBalance());
                i.setCurrentOrderNum(accountTrading.getPositions().size());
                i.setTotalCrossWalletBalance(new BigDecimal(i.getTotalUnrealizedProfit()).add(new BigDecimal(i.getTotalWalletBalance())).toString());
                apikeyService.updateById(i);
            });
        });
    }

    //    @Scheduled(cron = "5 * * * * *")
    public void gettTakerBuySellVol() {
        ThreadUtils.asyncSumbit(() -> {
            List<BinanceExchangeinfoUser> exchangeinfoUsers = exchangeinfoUserService.list();
            exchangeinfoUsers.forEach(i -> {
                Market market = new UMFuturesClientImpl().market();
                LinkedHashMap<String, Object> newParameters = new LinkedHashMap<>();
                newParameters.put("symbol", i.getSymbol());
                newParameters.put("contractType", ContractTypeEnum.PERPETUAL.getCode());
                newParameters.put("period", "5m");
                newParameters.put("limit", 1);
                List<TakerBuySellVolRespone> takerBuySellVolRespones = market.takerBuySellVol(newParameters);
                takerBuySellVolRespones.forEach(j -> {
                    log.info("交易对{} 买卖比率{} 主动买入量{} 主动卖出量{} 时间{}", i.getSymbol(), j.getBuySellRatio(), j.getBuyVol(), j.getSellVol(), LocalDateTimeUtil.parse(LocalDateTimeUtil.parseTimestamp(j.getTimestamp())));
                });
            });
        });
    }

    @Scheduled(cron = "3 * * * * *")
    public void cacheTickerPrice24() {
        String TICKER_PRICE24_KEY = "binance24:";
        Market market = new UMFuturesClientImpl().market();
        LinkedHashMap<String, Object> newParameters = new LinkedHashMap<>();
        List<TickerPrice24Response> tickerPrice24Responses = market.tickerPrice24(newParameters);
        tickerPrice24Responses.stream()
                .filter(i -> i.getSymbol().endsWith("USDT"))
                .forEach(i -> {
                    String key = TICKER_PRICE24_KEY + i.getSymbol();
                    String s = JSONObject.toJSONString(i);
                    redisTemplate.opsForValue().set(key, s, 10, TimeUnit.MINUTES);
                });
    }

    @Scheduled(cron = "1 * * * * *")
    public void cleanSymbolPrice() {
        LocalDateTime subtract = LocalDateTimeUtil.subtract(LocalDateTime.now(), 61, ChronoUnit.MINUTES);
        List<BinanceTickerPrice> cleanList = lambdaQuery().lt(BinanceTickerPrice::getPullTime, subtract).list();
        List<String> ids = cleanList.stream().map(BinanceTickerPrice::getId).collect(Collectors.toList());
        if (!ids.isEmpty()) {
            removeByIds(ids);
        }
    }

    @Scheduled(cron = "1 * * * * *")
    public void updateOrderStatus() {
        List<BinanceApikey> apikeys = apikeyService.getAllList();
        Map<String, List<BinanceOrder>> binanceOrderMap = binanceOderService.lambdaQuery()
                .eq(BinanceOrder::getStatus, OrderStatusEnum.NEW.getCode())
                .list().stream().collect(Collectors.groupingBy(BinanceOrder::getUserId));
        apikeys.forEach(i -> {
            ThreadUtils.asyncSumbit(() -> {
                if (binanceOrderMap.containsKey(i.getUserId())) {
                    List<BinanceOrder> binanceOrders = binanceOrderMap.get(i.getUserId());
                    binanceOrders.forEach(binanceOrder -> {
                        CommonOrderResponse commonOrderResponse = tradingService.queryOrder(i.getApiKey(), i.getSecretKey(), binanceOrder.getSymbol(), binanceOrder.getBinanceOrderId());
                        if (commonOrderResponse != null) {
                            binanceOrder.setStatus(commonOrderResponse.getStatus());
                            binanceOrder.setDepartureTime(LocalDateTimeUtil.parseTimestamp(commonOrderResponse.getUpdateTime()));
                            binanceOrder.setCumQuote(commonOrderResponse.getCumQuote());
                            binanceOrder.setExecutedQty(commonOrderResponse.getExecutedQty());
                            binanceOrder.setOrigQty(commonOrderResponse.getOrigQty());
                            binanceOrder.setPrice(commonOrderResponse.getPrice());
                            if (commonOrderResponse.getStatus().equals(OrderStatusEnum.FILLED.getCode()) && (binanceOrder.getType().equals(OrderTypesEnum.STOP_MARKET.getType()) || binanceOrder.getType().equals(OrderTypesEnum.TAKE_PROFIT_MARKET.getType()))) {
                                List<UserTradesResponse> userTradesResponses = tradingService.queryUserTrades(i.getApiKey(), i.getSecretKey(), binanceOrder.getSymbol(), binanceOrder.getBinanceOrderId(), null, null);
                                if (!userTradesResponses.isEmpty()) {
                                    BigDecimal commission = userTradesResponses.stream().map(UserTradesResponse::getCommission).map(BigDecimal::new).reduce(BigDecimal.ZERO, BigDecimal::add);
                                    BigDecimal profit = userTradesResponses.stream().map(UserTradesResponse::getRealizedPnl).map(BigDecimal::new).reduce(BigDecimal.ZERO, BigDecimal::add);
                                    profit = profit.subtract(commission);
                                    binanceOrder.setProfit(profit.toString());
                                }
                            }
                        }
                    });
                    binanceOderService.updateBatchById(binanceOrders);
                    binanceOderService.lambdaUpdate()
                            .eq(BinanceOrder::getUserId, i.getUserId())
                            .ne(BinanceOrder::getStatus, OrderStatusEnum.NEW.getCode())
                            .ne(BinanceOrder::getStatus, OrderStatusEnum.PARTIALLY_FILLED.getCode())
                            .ne(BinanceOrder::getStatus, OrderStatusEnum.FILLED.getCode())
                            .remove();
                }
            });
        });
    }

    @Scheduled(cron = "0/20 * * * * *")
    public void priceStatistics() {
        Set<String> symbols = redisTemplate.keys("binance:*");
        List<BinanceApikey> list = apikeyService.getAllList();
        for (BinanceApikey i : list) {
            for (String redisSymbolKey : symbols) {
                String cacheString = redisTemplate.opsForValue().get(redisSymbolKey);
                if (!StringUtils.hasLength(cacheString)) {
                    continue;
                }
                BinancePriceChangeCacheVo cacheVo = JSONObject.parseObject(cacheString, BinancePriceChangeCacheVo.class);
                String symbol = cacheVo.getSymbol();
                String userId = i.getUserId();
                BigDecimal price = new BigDecimal(cacheVo.getNewTickerPrice()).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                BigDecimal quantity = i.getMaxOrderAmount().divide(price, cacheVo.getQuantityPrecision(), RoundingMode.HALF_DOWN);
                //优先级判断
                if (isBetween(cacheVo.getSecondChange30().abs(), new BigDecimal("0.5"), new BigDecimal("1.5"))) {
                    Boolean res = secondChange30Handel(userId, i.getApiKey(), i.getSecretKey(), price, cacheVo, symbol, quantity);
                    if (res) {
                        break;
                    }
                }
                if (isBetween(cacheVo.getSecondChange60().abs(), new BigDecimal("1.5"), new BigDecimal("2"))) {
                    Boolean res = secondChange60Handel(userId, i.getApiKey(), i.getSecretKey(), price, cacheVo, symbol, quantity);
                    if (res) {
                        break;
                    }
                }
                if (isBetween(cacheVo.getMinutesAmplitude1().abs(), new BigDecimal("3"), new BigDecimal("5"))) {
                    log.error("{} 1分钟振幅： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude1());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1.5").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    BigDecimal win = price.multiply(new BigDecimal("1.5").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    if (isCompareTo(cacheVo.getMinutesAmplitude1(), new BigDecimal("0"), 2) && isCompareTo(cacheVo.getSecondChange60(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else if (isCompareTo(cacheVo.getMinutesAmplitude1(), new BigDecimal("0"), 1) && isCompareTo(cacheVo.getSecondChange60(), new BigDecimal("0"), 1)) {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude2().abs(), new BigDecimal("5"), 2)) {
                    log.error("{} 2分钟振幅： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude2());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("0.5").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("0.8").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    BigDecimal win = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    if (isCompareTo(cacheVo.getMinutesAmplitude1(), new BigDecimal("0"), 2) && isCompareTo(cacheVo.getSecondChange60(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else if (isCompareTo(cacheVo.getMinutesAmplitude1(), new BigDecimal("0"), 1) && isCompareTo(cacheVo.getSecondChange60(), new BigDecimal("0"), 1)) {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude60().abs(), new BigDecimal("70"), 2)) {
                    // 60分钟级别
                    log.error("{} 60分钟价格变化： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude60());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("4").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    BigDecimal win = price.multiply(new BigDecimal("10").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    if (isCompareTo(cacheVo.getMinutesAmplitude60(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude30().abs(), new BigDecimal("60"), 2)) {
                    // 30分钟级别
                    log.error("{} 30分钟价格变化： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude30());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("4").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    BigDecimal win = price.multiply(new BigDecimal("8").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                    if (isCompareTo(cacheVo.getMinutesAmplitude30(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude15().abs(), new BigDecimal("40"), 2)) {
                    // 15分钟级别
                    log.error("{} 15分钟价格变化： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude15());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    BigDecimal win = price.multiply(new BigDecimal("5").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    if (isCompareTo(cacheVo.getMinutesAmplitude15(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude10().abs(), new BigDecimal("30"), 2)) {
                    // 10分钟级别
                    log.error("{} 10分钟价格变化： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude10());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    BigDecimal win = price.multiply(new BigDecimal("5").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    if (isCompareTo(cacheVo.getMinutesAmplitude10(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                        break;
                    } else {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                        break;
                    }
                }
                if (isCompareTo(cacheVo.getMinutesAmplitude5().abs(), new BigDecimal("20"), 2)) {
                    // 5分钟级别
                    log.error("{} 5分钟价格变化： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getMinutesAmplitude5());
                    BigDecimal chaPrice = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
                    BigDecimal stop = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    BigDecimal win = price.multiply(new BigDecimal("5").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_UP);
                    if (isCompareTo(cacheVo.getMinutesAmplitude5(), new BigDecimal("0"), 2)) {
                        //开空
                        price = price.add(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
                    } else {
                        //做多
                        price = price.subtract(chaPrice);
                        price = price.setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
                        createOrderForSide(userId, i.getApiKey(), i.getSecretKey(), symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
                    }
                }
            }
        }
    }

    private Boolean secondChange60Handel(String userId, String apiKey, String secretKey, BigDecimal price, BinancePriceChangeCacheVo cacheVo, String symbol, BigDecimal quantity) {
        log.error("{} 60秒价格变化大于1%： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getSecondChange60());
        BigDecimal secondChange60 = cacheVo.getSecondChange60();
        BigDecimal minutesAmplitude2 = cacheVo.getMinutesAmplitude2();
        BigDecimal minutesAmplitude5 = cacheVo.getMinutesAmplitude5();
        BigDecimal minutesAmplitude10 = cacheVo.getMinutesAmplitude10();
        BigDecimal minutesAmplitude15 = cacheVo.getMinutesAmplitude15();
        BigDecimal minutesAmplitude30 = cacheVo.getMinutesAmplitude30();
        BigDecimal minutesAmplitude60 = cacheVo.getMinutesAmplitude60();
        boolean isAbsSecond60 = isCompareTo(secondChange60, new BigDecimal("0"), 2);
        boolean isAbsMinutes2 = isCompareTo(minutesAmplitude2, new BigDecimal("0"), 2);
        boolean isAbsMinutes5 = isCompareTo(minutesAmplitude5, new BigDecimal("0"), 2);
        boolean isAbsMinutes10 = isCompareTo(minutesAmplitude10, new BigDecimal("0"), 2);
        boolean isAbsMinutes15 = isCompareTo(minutesAmplitude15, new BigDecimal("0"), 2);
        //振幅
        boolean isMinutes2 = isBetween(minutesAmplitude2.abs(), new BigDecimal("1"), new BigDecimal("2"));
        boolean isMinutes5 = isBetween(minutesAmplitude5.abs(), new BigDecimal("1"), new BigDecimal("3"));
        boolean isMinutes15 = isBetween(minutesAmplitude15.abs(), new BigDecimal("1"), new BigDecimal("3"));
        boolean isOther3Minutes30 = isCompareTo(minutesAmplitude30.abs(), new BigDecimal("5"), 2);
        boolean isOther3Minutes60 = isCompareTo(minutesAmplitude60.abs(), new BigDecimal("5"), 2);
        boolean isOther6Minutes5 = isCompareTo(minutesAmplitude5.abs(), new BigDecimal("10"), 2);
        boolean isOther6Minutes15 = isCompareTo(minutesAmplitude15.abs(), new BigDecimal("10"), 2);
        BigDecimal chaPrice = price.multiply(new BigDecimal("0.4").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
        BigDecimal stop = price.multiply(new BigDecimal("1.3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
        BigDecimal win = price.multiply(new BigDecimal("2").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
        if (isAbsSecond60 && isAbsMinutes2 && isMinutes2 && isAbsMinutes5 && isAbsMinutes10 && isMinutes5 && isMinutes15 && !isOther3Minutes30 && !isOther3Minutes60) {
            //BUY
            price = price.subtract(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
            return true;
        } else if (!isAbsSecond60 && (isAbsMinutes5 && isOther6Minutes5 || isAbsMinutes15 && isOther6Minutes15) && !isOther3Minutes30 && !isOther3Minutes60) {
            //BUY
            chaPrice = price.multiply(new BigDecimal("1.5").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
            price = price.subtract(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            stop = price.multiply(new BigDecimal("2").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            win = price.multiply(new BigDecimal("4").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
            return true;
        } else if (!isAbsSecond60 && !isAbsMinutes2 && isMinutes2 && !isAbsMinutes5 && !isAbsMinutes10 && isMinutes5 && isMinutes15 && !isOther3Minutes30 && !isOther3Minutes60) {
            //SELL
            price = price.add(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
            return true;
        } else if (isAbsSecond60 && (!isAbsMinutes5 && isOther6Minutes5 || !isAbsMinutes15 && isOther6Minutes15) && !isOther3Minutes30 && !isOther3Minutes60) {
            //SELL
            chaPrice = price.multiply(new BigDecimal("1.5").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
            price = price.add(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            stop = price.multiply(new BigDecimal("2").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            win = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
            return true;
        }
        return false;
    }

    //30秒价格处理
    private Boolean secondChange30Handel(String userId, String apiKey, String secretKey, BigDecimal price, BinancePriceChangeCacheVo cacheVo, String symbol, BigDecimal quantity) {
        log.error("{} 30秒价格变化大于1%： 当前价{}  百分比变化：{}%", symbol, price, cacheVo.getSecondChange30());
        BigDecimal secondChange30 = cacheVo.getSecondChange30();
        BigDecimal secondChange60 = cacheVo.getSecondChange60();
        BigDecimal minutesAmplitude2 = cacheVo.getMinutesAmplitude2();
        BigDecimal minutesAmplitude5 = cacheVo.getMinutesAmplitude5();
        BigDecimal minutesAmplitude10 = cacheVo.getMinutesAmplitude10();
        BigDecimal minutesAmplitude15 = cacheVo.getMinutesAmplitude15();
        BigDecimal minutesAmplitude30 = cacheVo.getMinutesAmplitude30();
        BigDecimal minutesAmplitude60 = cacheVo.getMinutesAmplitude60();
        boolean isAbsSecond30 = isCompareTo(secondChange30, new BigDecimal("0"), 2);
        boolean isAbsSecond60 = isCompareTo(secondChange60, new BigDecimal("0"), 2);
        boolean isAbsMinutes5 = isCompareTo(minutesAmplitude5, new BigDecimal("0"), 2);
        boolean isAbsMinutes10 = isCompareTo(minutesAmplitude10, new BigDecimal("0"), 2);
        boolean isAbsMinutes15 = isCompareTo(minutesAmplitude15, new BigDecimal("0"), 2);
        //振幅
        boolean isSecond60 = isBetween(secondChange60.abs(), new BigDecimal("0.7"), new BigDecimal("1.5"));
        boolean isMinutes2 = isBetween(minutesAmplitude2.abs(), new BigDecimal("0.7"), new BigDecimal("3"));
        boolean isMinutes5 = isBetween(minutesAmplitude5.abs(), new BigDecimal("0.7"), new BigDecimal("3"));
        boolean isMinutes10 = isBetween(minutesAmplitude10.abs(), new BigDecimal("0.7"), new BigDecimal("3"));
        boolean isOther3Minutes30 = isCompareTo(minutesAmplitude30.abs(), new BigDecimal("5"), 2);
        boolean isOther3Minutes60 = isCompareTo(minutesAmplitude60.abs(), new BigDecimal("5"), 2);
        boolean isOther6Minutes5 = isCompareTo(minutesAmplitude5.abs(), new BigDecimal("10"), 2);
        boolean isOther6Minutes15 = isCompareTo(minutesAmplitude15.abs(), new BigDecimal("10"), 2);
        BigDecimal chaPrice = price.multiply(new BigDecimal("0.3").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
        BigDecimal stop = price.multiply(new BigDecimal("1").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
        BigDecimal win = price.multiply(new BigDecimal("1.5").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
        if (isAbsSecond30 && isAbsSecond60 && isAbsMinutes5 && isSecond60 && isMinutes2 && isMinutes5 && isMinutes10 && isAbsMinutes10 && !isOther3Minutes30 && !isOther3Minutes60) {
            //BUY
            price = price.subtract(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
            return true;
        } else if (!isAbsSecond30 && !isAbsSecond60 && !isAbsMinutes5 && isSecond60 && isMinutes2 && isMinutes5 && isMinutes10 && !isAbsMinutes10 && !isOther3Minutes30 && !isOther3Minutes60) {
            //SELL
            price = price.add(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
            return true;
        } else if (!isAbsSecond30 && ((isAbsMinutes5 && isOther6Minutes5) || (isAbsMinutes15 && isOther6Minutes15)) && !isOther3Minutes30 && !isOther3Minutes60) {
            //BUY
            chaPrice = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
            price = price.subtract(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            stop = price.multiply(new BigDecimal("2").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            win = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.BUY, PositionSideEnum.LONG, price, quantity, stop, win);
            return true;
        } else if (isAbsSecond30 && ((!isAbsMinutes5 && isOther6Minutes5) || (!isAbsMinutes15 && isOther6Minutes15)) && !isOther3Minutes30 && !isOther3Minutes60) {
            //SELL
            chaPrice = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"), cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN));
            price = price.add(chaPrice).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            stop = price.multiply(new BigDecimal("2").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            win = price.multiply(new BigDecimal("3").divide(new BigDecimal("100"))).setScale(cacheVo.getPricePrecision(), RoundingMode.HALF_DOWN);
            createOrderForSide(userId, apiKey, secretKey, symbol, SideEnum.SELL, PositionSideEnum.SHORT, price, quantity, stop, win);
            return true;
        }
        return false;
    }

    private void cacheSymbolPrice() {
        try {
            List<BinanceTickerPrice> allList = lambdaQuery().list();
            if (allList.isEmpty()) {
                return;
            }
            Map<String, List<BinanceTickerPrice>> symbolMap = allList.stream()
                    .collect(Collectors.groupingBy(BinanceTickerPrice::getSymbol, Collectors.collectingAndThen(
                            Collectors.toList(),
                            list -> list.stream()
                                    .sorted(Comparator.comparing(BinanceTickerPrice::getPullTime).reversed())
                                    .collect(Collectors.toList()))));
            symbolMap.forEach((symbol, values) -> {
                if (values.size() > 3) {
                    String newTickerPrice = values.get(0).getPrice();
                    BinancePriceChangeCacheVo vo = calculateChangeRates(values, newTickerPrice);
                    redisTemplate.opsForValue().set("binance:" + symbol, JSONObject.toJSONString(vo), 120, TimeUnit.SECONDS);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private BinancePriceChangeCacheVo calculateChangeRates(List<BinanceTickerPrice> allList, String newTickerPrice) {
        BinancePriceChangeCacheVo vo = new BinancePriceChangeCacheVo();
        BinanceTickerPrice tickerPrice = allList.get(0);
        vo.setSymbol(tickerPrice.getSymbol());
        vo.setPricePrecision(tickerPrice.getPricePrecision());
        vo.setQuantityPrecision(tickerPrice.getQuantityPrecision());
        LocalDateTime now = LocalDateTime.now();
        //15分钟k线
        vo.setPriceChange15Vos(computePriceChangeK(allList, now, 15));
        vo.setPriceChange30Vos(computePriceChangeK(allList, now, 30));
        // 获取不同时间点的价格
        String secondTickerPrice30 = getPriceAtTime(allList, now.minusSeconds(30));
        String secondTickerPrice60 = getPriceAtTime(allList, now.minusSeconds(60));
        // 计算价格变化率
        BigDecimal secondChange30 = calculateChangeRate(secondTickerPrice30, newTickerPrice);
        BigDecimal secondChange60 = calculateChangeRate(secondTickerPrice60, newTickerPrice);
        // 振幅
        BigDecimal minutesAmplitude1 = calculateAmplitude(allList, now, now.minusMinutes(1));
        BigDecimal minutesAmplitude2 = calculateAmplitude(allList, now, now.minusMinutes(2));
        BigDecimal minutesAmplitude5 = calculateAmplitude(allList, now, now.minusMinutes(5));
        BigDecimal minutesAmplitude10 = calculateAmplitude(allList, now, now.minusMinutes(10));
        BigDecimal minutesAmplitude15 = calculateAmplitude(allList, now, now.minusMinutes(15));
        BigDecimal minutesAmplitude30 = calculateAmplitude(allList, now, now.minusMinutes(30));
        BigDecimal minutesAmplitude60 = calculateAmplitude(allList, now, now.minusMinutes(60));
        vo.setNewTickerPrice(newTickerPrice);
        vo.setSecondChange30(secondChange30);
        vo.setSecondChange60(secondChange60);
        vo.setMinutesAmplitude1(minutesAmplitude1);
        vo.setMinutesAmplitude2(minutesAmplitude2);
        vo.setMinutesAmplitude5(minutesAmplitude5);
        vo.setMinutesAmplitude10(minutesAmplitude10);
        vo.setMinutesAmplitude15(minutesAmplitude15);
        vo.setMinutesAmplitude30(minutesAmplitude30);
        vo.setMinutesAmplitude60(minutesAmplitude60);
        return vo;
    }

    private List<PriceChangeVo> computePriceChangeK(List<BinanceTickerPrice> allList, LocalDateTime now, int k) {
        now = LocalDateTimeUtil.parse(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "yyyy-MM-dd HH:mm");
        now = now.minusMinutes(now.getMinute() % k);
        int num = 60 / k;
        List<PriceChangeVo> kLines = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            LocalDateTime startTime = now.minusMinutes(k * (num - 1 - i));
            LocalDateTime endTime = startTime.plusMinutes(k);
            List<BinanceTickerPrice> intervalPrices = allList.stream()
                    .filter(p -> !p.getPullTime().isBefore(startTime) && p.getPullTime().isBefore(endTime))
                    .sorted(Comparator.comparing(BinanceTickerPrice::getPullTime))
                    .collect(Collectors.toList());

            if (!intervalPrices.isEmpty() && intervalPrices.size() > 1) {
                BigDecimal open = new BigDecimal(intervalPrices.get(0).getPrice());
                BigDecimal close = new BigDecimal(intervalPrices.get(intervalPrices.size() - 1).getPrice());
                BinanceTickerPrice highTickerPrice = intervalPrices.stream().max(Comparator.comparing(price -> new BigDecimal(price.getPrice()))).get();
                BinanceTickerPrice lowTickerPrice = intervalPrices.stream().min(Comparator.comparing(price -> new BigDecimal(price.getPrice()))).get();
                BigDecimal high = new BigDecimal(highTickerPrice.getPrice());
                BigDecimal low = new BigDecimal(lowTickerPrice.getPrice());
                BigDecimal amplitude = percentageChange(lowTickerPrice.getPrice(), highTickerPrice.getPrice()).getChangePercentage();
                BigDecimal range = percentageChange(open.toString(), close.toString()).getChangePercentage().negate();
                if (lowTickerPrice.getPullTime().isAfter(highTickerPrice.getPullTime())) {
                    amplitude = amplitude.abs().negate();
                } else {
                    amplitude = amplitude.abs();
                }
                kLines.add(new PriceChangeVo(startTime, endTime, low, high, amplitude, range, open, close));
            }
        }
        kLines = kLines.stream().sorted(Comparator.comparing(PriceChangeVo::getStartTime).reversed()).collect(Collectors.toList());
        return kLines;
    }

    private BigDecimal calculateAmplitude(List<BinanceTickerPrice> allList, LocalDateTime end, LocalDateTime start) {
        Optional<BinanceTickerPrice> max;
        Optional<BinanceTickerPrice> min;
        try {
            max = Optional.ofNullable(allList.stream()
                    .filter(price -> price.getPullTime().isAfter(start) && price.getPullTime().isBefore(end)) // 时间在范围内
                    .max(Comparator.comparing(price -> new BigDecimal(price.getPrice()))).orElse(null));

            min = Optional.ofNullable(allList.stream()
                    .filter(price -> price.getPullTime().isAfter(start) && price.getPullTime().isBefore(end)) // 时间在范围内
                    .min(Comparator.comparing(price -> new BigDecimal(price.getPrice()))).orElse(null));
            if (!max.isPresent() || !min.isPresent()) {
                return BigDecimal.ZERO;
            }
            BinanceTickerPrice maxPrice = max.get();
            BinanceTickerPrice minPrice = min.get();
            BigDecimal changePercentage = percentageChange(maxPrice.getPrice(), minPrice.getPrice()).getChangePercentage();
            if (minPrice.getPullTime().isAfter(maxPrice.getPullTime())) {
                return changePercentage.abs().negate();
            } else {
                return changePercentage.abs();
            }
        } catch (Exception e) {
            String symbol = allList.get(0).getSymbol();
            log.info("价格异常，删除缓存 {}", allList.get(0).getSymbol());
            redisTemplate.delete("binance:" + allList.get(0).getSymbol());
            remove(new LambdaQueryWrapper<BinanceTickerPrice>().eq(BinanceTickerPrice::getSymbol, symbol));
            throw e;
        }
    }


    private BigDecimal calculateChangeRate(String pastPrice, String currentPrice) {
        if (pastPrice == null || currentPrice == null) {
            return BigDecimal.ZERO;
        }
        return percentageChange(currentPrice, pastPrice).getChangePercentage();
    }

    private String getPriceAtTime(List<BinanceTickerPrice> allList, LocalDateTime targetTime) {
        Optional<BinanceTickerPrice> targetPrice = allList.stream()
                // 过滤出时间早于 targetTime 的数据
                .filter(price -> price.getPullTime().isBefore(targetTime))
                // 选择与 targetTime 最近的价格
                .min(Comparator.comparingLong(price -> Math.abs(price.getPullTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - targetTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())));
        // 如果没有找到返回null
        return targetPrice.map(BinanceTickerPrice::getPrice).orElse(null);
    }

    private PriceChange percentageChange(String priceStart, String priceEnd) {
        // 获取价格并转换为 BigDecimal 以避免浮点数计算误差
        BigDecimal price = new BigDecimal(priceStart);
        BigDecimal lastPrice = new BigDecimal(priceEnd);
        // 计算价格差异
        BigDecimal priceDifference = price.subtract(lastPrice);
        // 计算百分比变化，避免除以零的情况
        BigDecimal percentageChange = BigDecimal.ZERO;
        if (lastPrice.compareTo(BigDecimal.ZERO) != 0) {
            percentageChange = priceDifference.divide(lastPrice, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
        }
        // 将变化百分比保留两位小数
        BigDecimal formattedPercentage = percentageChange.setScale(2, RoundingMode.HALF_UP);
        return new PriceChange(priceDifference, formattedPercentage);
    }

    //判断数值是否在给定的范围之间
    public boolean isBetween(BigDecimal value, BigDecimal lower, BigDecimal upper) {
        return value.compareTo(lower) >= 0 && value.compareTo(upper) <= 0;
    }

    public boolean isCompareTo(BigDecimal lower, BigDecimal upper, Integer type) {
        switch (type) {
            case 1:
                return lower.compareTo(upper) < 0;
            case 2:
                return lower.compareTo(upper) > 0;
            case 3:
                return lower.compareTo(upper) == 0;
            default:
                return false;
        }
    }

    //创建订单公共方法
    private void createOrderForSide(String userId, String apiKey, String secretKey, String symbol, SideEnum side,
                                    PositionSideEnum positionSide, BigDecimal price,
                                    BigDecimal quantity, BigDecimal stop, BigDecimal win) {
        String SYMBOL_KEY = "Order:" + symbol + ":" + apiKey;
        String CREATE_KEY = "Order:" + apiKey;
        CommonOrderDto dto = CommonOrderDto.builder().symbol(symbol).side(side).positionSide(positionSide).userId(userId)
                .type(OrderTypesEnum.LIMIT).price(price).quantity(quantity).build();
        log.error("appkey {} 开始下单{} 下单价格 {} 开单方向 {}", apiKey, symbol, price, side.getDescription());
        try {
            //是否正在下单
            if (redisTemplate.hasKey(CREATE_KEY)) {
                return;
            }
            if (redisTemplate.hasKey(SYMBOL_KEY)) {
                log.error("appkey {} 当前交易对 在5分钟内以下过单 {}", apiKey, symbol);
                return;
            }
            BinanceApikey apikey = apikeyService.lambdaQuery().eq(BinanceApikey::getApiKey, apiKey).last("limit 1").one();
            Integer positionNum = apikey.getCurrentOrderNum();
            if (positionNum >= apikey.getMaxOrderNum()) {
                log.error("appkey {} 当前交易对 超出下单限制 {}", apiKey, symbol);
                return;
            }
            redisTemplate.opsForValue().set(CREATE_KEY, "1", 3, TimeUnit.MINUTES);
            //下单
            log.info("下单参数{}", JSONObject.toJSONString(dto));
            tradingService.createOrder(apiKey, secretKey, dto);
            redisTemplate.opsForValue().set(SYMBOL_KEY, "1", 5, TimeUnit.MINUTES);
            //止损单
            tradingService.createOrder(apiKey, secretKey, CommonOrderDto.builder().symbol(symbol).side(side == SideEnum.SELL ? SideEnum.BUY : SideEnum.SELL)
                    .positionSide(positionSide).type(OrderTypesEnum.STOP_MARKET).stopPrice(side == SideEnum.SELL ? price.add(stop) : price.subtract(stop)).userId(userId)
                    .quantity(quantity).build());
            //止盈单
            tradingService.createOrder(apiKey, secretKey, CommonOrderDto.builder().symbol(symbol).side(side == SideEnum.SELL ? SideEnum.BUY : SideEnum.SELL)
                    .positionSide(positionSide).type(OrderTypesEnum.TAKE_PROFIT_MARKET).stopPrice(side == SideEnum.SELL ? price.subtract(win) : price.add(win)).userId(userId)
                    .quantity(quantity).build());
            log.error("创建订单成功{} 下单价格 {} 开单方向 {}", symbol, price, side.getDescription());
            apikeyService.lambdaUpdate().set(BinanceApikey::getCurrentOrderNum, positionNum + 1).eq(BinanceApikey::getApiKey, apiKey).update();
        } catch (ErrorException e) {
            log.error("下单失败，稍后重试 {} {}", e.getCode(), e.getMsg());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisTemplate.delete(CREATE_KEY);
        }
    }
}
